package com.zhuyuan.common.log.aspect;

import com.alibaba.fastjson2.JSON;
import com.zhuyuan.common.log.annotation.Log;
import com.zhuyuan.common.log.enums.OperateStatus;
import com.zhuyuan.common.log.filter.FiledExcludeFilter;
import com.zhuyuan.common.log.service.AsyncLogService;
import com.zhuyuan.core.util.IpUtils;
import com.zhuyuan.core.util.ServletUtils;
import com.zhuyuan.core.util.StringUtils;
import com.zhuyuan.security.util.SecurityUtils;
import com.zhuyuan.system.api.domain.SysOperLog;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;


/**
 * @Author: 张琳凯
 * @Description: 记录操作日志
 * @DateTime: 2024/12/19 13:35
 **/
@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    /**
     * 排除字段
     */
    private static final String[] excludeFields = {"password"};
    /**
     * 消耗时间
     */
    private static final ThreadLocal<Long> consumeTime = new NamedThreadLocal<>("Consume Time");

    @Autowired
    private AsyncLogService asyncLogService;

    /**
     * 拦截前 记录当前时间，用于计算过程消耗的时间
     *
     * @param: [joinPoint 切点, log 注解]
     */
    @Before(value = "@annotation(log)")
    public void doBefore(JoinPoint joinPoint, Log log) {
        consumeTime.set(System.currentTimeMillis());
    }

    /**
     * 请求后执行
     *
     * @param: [joinPoint 切点, log 注解, jsonResult 返回结果]
     */
    @AfterReturning(pointcut = "@annotation(log)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log log, Object jsonResult) {
        handleLog(joinPoint, log, jsonResult, null);
    }

    /**
     * 拦截异常
     *
     * @param joinPoint 切点
     * @param: [joinPoint 切点, log 注解, e 异常]
     */
    @AfterThrowing(value = "@annotation(log)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {
        handleLog(joinPoint, log, null, e);
    }

    /**
     * @description: 处理注解
     * @author: 张琳凯
     * @date: 2024/12/19 15:25
     * @param: [joinPoint, log, jsonResult, e]
     * @return: void
     **/
    private void handleLog(JoinPoint joinPoint, Log log, Object jsonResult, Exception exception) {
        try {
            String ip = IpUtils.getIpAddr();
            String url = ServletUtils.getRequest().getRequestURI();
            String userName = SecurityUtils.getUsername();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            String requestMethod = ServletUtils.getRequest().getMethod();
            Long time = System.currentTimeMillis() - consumeTime.get();
            int operateType = log.operaType().ordinal();
            String title = log.title();
            int sourceType = log.sourceType().ordinal();

            SysOperLog sysOperLog=new SysOperLog();
            if (exception==null){
                sysOperLog.setStatus(OperateStatus.SUCCESS.ordinal());
            }else{
                sysOperLog.setStatus(OperateStatus.FAIL.ordinal());
                String errorStr= StringUtils.substring(exception.getMessage(), 0, 2000);
                sysOperLog.setErrorMsg(errorStr);
            }
            sysOperLog.setOperIp(ip);
            sysOperLog.setOperUrl(url);
            sysOperLog.setOperName(userName);
            sysOperLog.setMethod(className + "." + methodName + "()");
            sysOperLog.setRequestMethod(requestMethod);
            sysOperLog.setCostTime(time);
            sysOperLog.setBusinessType(operateType);
            sysOperLog.setTitle(title);
            sysOperLog.setOperatorType(sourceType);
            if (log.isSaveRequestData()) {
                setRequestValue(joinPoint, sysOperLog, log.excludeParamNames());
            }
            if (log.isSaveResponseData()) {
                String responseResult = StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000);
                sysOperLog.setJsonResult(responseResult);
            }
            //保存到数据库
            asyncLogService.saveLogData(sysOperLog);
        } catch (Exception e) {
            logger.error("异常信息: ", e.getMessage());
            e.printStackTrace();
        } finally {
            consumeTime.remove();
        }

    }

    /**
     * 获取请求的参数，放到数据库对象SysOperLog中
     *
     * @param operLog 数据库日志存储对象
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception {
        ServletRequest servletRequest = ServletUtils.getRequest();
        Map<?, ?> paramsMap = ServletUtils.getParamMap(servletRequest);
        String requestMethod = operLog.getRequestMethod();
        String paramString = "";
        if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
            String params = paramsArrayToString(joinPoint.getArgs(), excludeParamNames);
            paramString = StringUtils.substring(params, 0, 2000);
        } else {
            paramString = StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000);
        }
        operLog.setOperParam(paramString);
    }

    private String paramsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object obj : paramsArray) {
                if (StringUtils.isNotNull(obj) && !isFilterObject(obj)) {
                    try {
                        String jsonObj = JSON.toJSONString(obj, excludePropertyPreFilter(excludeParamNames));
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 忽略敏感属性
     */
    public FiledExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
        return new FiledExcludeFilter().addExcludes(ArrayUtils.addAll(excludeFields, excludeParamNames));
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

}
